
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Diagnostics;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Soap;
using Swaf.Container;
using Swaf.Security;

namespace Swaf
{
	/// <summary>
	/// Summary description for ApplicationRemoteAccess.
	/// </summary>
	public class ApplicationRemoteServer : MarshalByRefObject
	{
		public ApplicationRemoteServer(string name, string httpPort, string tcpPort, IList additionalRemoteOptions)
		{
			if(!Application.globalApp.inBrowseMode)
			{
				IChannel channel = null;
				string url = "";
				string appUrl = "";


				if(tcpPort != null)
				{
					BinaryServerFormatterSinkProvider tpfProvider = new BinaryServerFormatterSinkProvider();
					tpfProvider.TypeFilterLevel = TypeFilterLevel.Full;

					BinaryClientFormatterSinkProvider clientProvider = new BinaryClientFormatterSinkProvider();

					IDictionary props = new Hashtable();
					props["port"] = Int32.Parse(tcpPort);
					props["name"] = "tcp" + name;
					foreach(string[] option in additionalRemoteOptions)
						props[option[0]] = option[1];

					//channel = new TcpServerChannel(Int32.Parse(tcpPort));
					channel = new TcpChannel( props, clientProvider, tpfProvider);
					ChannelServices.RegisterChannel(channel);
					RemotingConfiguration.RegisterWellKnownServiceType(typeof(ApplicationRemoteAccess), name, WellKnownObjectMode.SingleCall);
				}

				if(httpPort != null)
				{
					Hashtable options = new Hashtable();
					options["includeVersions"] = false;
					options["strictBinding"] = false;

					SoapServerFormatterSinkProvider soapProvider = new SoapServerFormatterSinkProvider(options,null);
					soapProvider.TypeFilterLevel = TypeFilterLevel.Full;
					//Swaf.Remoting.ServerSinkProvider soapProvider = new Swaf.Remoting.ServerSinkProvider();


					IDictionary props = new Hashtable();
					//props["port"] = Int32.Parse(httpPort);
					props["name"] = "http" + name;
					foreach (string[] option in additionalRemoteOptions)
						props[option[0]] = option[1];

					//channel = new HttpServerChannel(Int32.Parse(httpPort));
					channel = new HttpServerChannel(props, soapProvider);

					RemotingConfiguration.RegisterWellKnownServiceType(typeof(ApplicationRemoteAccess), "web" + name, WellKnownObjectMode.Singleton);
					ChannelServices.RegisterChannel(channel);
				}
				Application thisApp = (Application) Application.globalApp;
				thisApp.OnAppShutdown += new AppShutdownRequest(shutDown);
			}
		}

		public void shutDown(IApplication app)
		{
			foreach (IChannel channel in ChannelServices.RegisteredChannels)
			{
				ChannelServices.UnregisterChannel(channel);
			}

		}
	}

	public class ApplicationRemoteAccess : MarshalByRefObject, ISessionAppManagement
	{

		#region ISessionAppManagement Members
		public IApplication createSessionApplication(string sessionKey)
		{
			return createSessionApplication(sessionKey, false);
		}
        public IApplication createSessionApplication(string sessionKey, bool isTiedToGlobalApp)
        {
            return ((ISessionAppManagement)Application.globalApp).createSessionApplication(sessionKey, isTiedToGlobalApp);
        }
        public IApplication createSessionApplication(string sessionKey, bool isTiedToGlobalApp, IDictionary startingSessionValues)
        {
            return ((ISessionAppManagement)Application.globalApp).createSessionApplication(sessionKey, isTiedToGlobalApp, startingSessionValues);
        }
        public IApplication createSessionApplication(string sessionKey, ISecurityPrincipal currentPrincipal)
		{
			return createSessionApplication(sessionKey,false, currentPrincipal );
		}
        public IApplication createSessionApplication(string sessionKey, bool isTiedToGlobalApp, ISecurityPrincipal currentPrincipal)
        {
            return ((ISessionAppManagement)Application.globalApp).createSessionApplication(sessionKey, isTiedToGlobalApp, currentPrincipal);
        }
        public IApplication createSessionApplication(string sessionKey, bool isTiedToGlobalApp, ISecurityPrincipal currentPrincipal, IDictionary sessionStartValues)
        {
            return ((ISessionAppManagement)Application.globalApp).createSessionApplication(sessionKey, isTiedToGlobalApp, currentPrincipal, sessionStartValues);
        }

		public IApplication getExistingSession(string sessionKey)
		{
			return ((ISessionAppManagement)Application.globalApp).getExistingSession(sessionKey);
		}

		public void appSessionComplete(string sessionKey)
		{
			((ISessionAppManagement)Application.globalApp).appSessionComplete(sessionKey);
		}

		/// <summary>
		/// This method will validate the session and then set the current principal if necessary
		/// </summary>
		/// <param name="sessionApp"></param>
		/// <param name="isTiedToGlobalApp"></param>
		/// <param name="principalUserName"></param>
		/// <param name="principalAdditional"></param>
		/// <param name="principalInstanceId"></param>
		protected void ValidateSession(IApplication sessionApp, bool isTiedToGlobalApp, string principalUserName, INameAddressSupport principalAdditional, string principalInstanceId)
		{
            if (sessionApp == null)
                throw new InvalidSessionException(string.Format(
                "ValidateSession: Invalid sessionApp == null. principalUserName = {0}, principalAdditional is {1} null, principalInstanceId is {2}",
                principalUserName, principalAdditional == null?"":"not", principalInstanceId));

            ISessionInfo info = sessionApp.sessionInfo;
            if (info == null)
                throw new InvalidSessionException(string.Format(
                "ValidateSession: Invalid sessionApp.sessionInfo == null. principalUserName = {0}, principalAdditional is {1} null, principalInstanceId is {2}",
                principalUserName, principalAdditional == null?"":"not", principalInstanceId));

            if (!info.isValidSession)
                throw new InvalidSessionException(string.Format(
                "Session Id {0} is not a valid session" +
                "ValidateSession: Invalid sessionApp.sessionInfo == null. principalUserName = {1}, principalAdditional is {2} null, principalInstanceId is {3}",
                sessionApp.sessionInfo.sessionId, principalUserName, principalAdditional == null?"":"not", principalInstanceId));

            try
            {
                //We need to set the current principal with each call so we make sure to propagate any changes
                //downstream. 
                string thisInstanceId = info.principalInstanceId;
                string callerInstanceId = principalInstanceId;

                string thisUserName = info.principalUserName;
                string callerUserName = principalUserName;

                if (thisInstanceId == null)
                    throw new InvalidSessionException(string.Format(
                        "ValidateSession: Invalid thisInstanceId == null. principalUserName = {0}, principalAdditional is {1} null, principalInstanceId is {2}",
                        principalUserName, principalAdditional == null ? "" : "not", principalInstanceId));
                if (thisUserName == null)
                    throw new InvalidSessionException(string.Format(
                        "ValidateSession: Invalid thisUserName == null. principalUserName = {0}, principalAdditional is {1} null, principalInstanceId is {2}",
                        principalUserName, principalAdditional == null ? "" : "not", principalInstanceId));

                // Reset the principal if it wasn't already handled and the information is different
                if (!info.principalWasReset && !(thisInstanceId.Equals(callerInstanceId) || thisUserName.Equals(callerUserName))
                    && principalUserName != null)
                {
                    // Safely establish the new principal.  
                    // This mechanism solves the issues with serializing principals accross the wire.
                    sessionApp.securityMgr.setCurrentPrincipal(principalUserName, principalAdditional, principalInstanceId);
                }
            }
            catch (InvalidSessionException)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new InvalidSessionException(string.Format(
                     "ValidateSession: Error establishing session for rule call. principalUserName = {0}, principalAdditional is {1} null, principalInstanceId is {2}",
                principalUserName, principalAdditional == null ? "" : "not", principalInstanceId), e);
            }
		}

		/// <summary>
		/// executeRule is used to handle remote rule calls.  It will take the session Id to either find the current
		/// session or create a new session.  It will then validate the sessionApplication that is to be used.
		/// The sessionApp will then be the context to execute the rule and validate the session through any supplied
		/// session validator.
		/// </summary>
		/// <param name="sessionId"></param>
		/// <param name="isTiedToGlobalApp"></param>
		/// <param name="userName"></param>
		/// <param name="additionalInfo"></param>
		/// <param name="instanceId"></param>
		/// <param name="methodName"></param>
		/// <param name="ruleParams"></param>
		/// <param name="multiCall"></param>
		/// <returns></returns>
		public IResults executeRule(string sessionId, bool isTiedToGlobalApp, string userName, INameAddressSupport additionalInfo, string instanceId,  string currentCulture,
			string methodName, ICollection ruleParams, bool multiCall)
		{
			IResults results = null;

			// Need to create session
			IApplication sessionApp = (IApplication) ((ISessionAppManagement)Application.globalApp).createSessionApplication(sessionId, isTiedToGlobalApp);
			
			// Set up correct session application
			IApplication currApp = Application.currentApp;
			Application.currentApp = sessionApp;

			try
			{ 
				// Validate the session returning the session app
				ValidateSession(sessionApp, isTiedToGlobalApp, userName, additionalInfo, instanceId);

                //Set up the culture for the new session app.
				//TODO: Deal with culture properly.
                //sessionApp.resMgr.CurrentCulture = setCurrentCulture(currentCulture);

				// Execute the requested rule
				results = sessionApp.ruleMgr.execute(methodName, ruleParams, multiCall);
			}
			catch (InvalidSessionException)
			{
				throw;
			}
			catch (Exception e)
			{
				throw new Swaf.Call.CallException(string.Format("Failed to call method {0} for user {1} on system {2}.", methodName, userName, Environment.MachineName), e);
			}
			finally
			{
				// Reset the current application
				Application.currentApp = currApp;
			}

			return results;
		}

		/// <summary>
		/// executeRule is used to handle remote rule calls.  It will take the session Id to either find the current
		/// session or create a new session.  It will then validate the sessionApplication that is to be used.
		/// The sessionApp will then be the context to execute the rule and validate the session through any supplied
		/// session validator.
		/// </summary>
		/// <param name="sessionId"></param>
		/// <param name="isTiedToGlobalApp"></param>
		/// <param name="userName"></param>
		/// <param name="additionalInfo"></param>
		/// <param name="instanceId"></param>
		/// <param name="methodName"></param>
		/// <param name="ruleParams"></param>
		/// <returns></returns>
		public IResults executeRule(string sessionId, bool isTiedToGlobalApp, string userName, INameAddressSupport additionalInfo, string instanceId, string currentCulture,
			string methodName, object ruleParams)
		{
			IResults results = null;

			// Need to create session
			IApplication sessionApp = (IApplication) ((ISessionAppManagement)Application.globalApp).createSessionApplication(sessionId, isTiedToGlobalApp);
			
			// Set up correct session application
			IApplication currApp = Application.currentApp;
			Application.currentApp = sessionApp;

			try
			{
				// Validate the session returning the session app
				ValidateSession(sessionApp, isTiedToGlobalApp, userName, additionalInfo, instanceId);

                //Set up the culture for the new session app.
				//TODO: Deal with culture properly
                //sessionApp.setCurrentCulture(currentCulture);
				// Execute the requested rule
				if(ruleParams == null)
					results = sessionApp.ruleMgr.execute(methodName);
				else
					results = sessionApp.ruleMgr.execute(methodName, ruleParams);
			}
			catch (InvalidSessionException)
			{
				throw;
			}
			catch (Exception e)
			{
				throw new Swaf.Call.CallException(string.Format("Failed to call method {0} for user {1} on system {2}.", methodName, userName, Environment.MachineName), e);
			}
			finally
			{
				// Reset the current application
				Application.currentApp = currApp;
			}

			return results;
		}

		#endregion
	}
}
